package middleware

import (
	"bytes"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"runtime/debug"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"

	"gitee.com/git-lz/twelve/common/xtrace"
	"gitee.com/git-lz/twelve/common/zaplog"
)

// GinRecovery recover掉项目可能出现的panic
func GinRecovery(stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			start := time.Now()
			if err := recover(); err != nil {
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||
							strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}
				ctx := xtrace.ExtractTrueCtx(c)

				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					zaplog.Errorf(ctx, c.Request.URL.Path, start,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
					// If the connection is dead, we can't write a status to it.
					c.Error(err.(error)) // nolint: errcheck
					c.Abort()
					return
				}

				if stack {
					zaplog.Errorf(ctx, "[Recovery from panic]", start,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())),
					)
				} else {
					zaplog.Errorf(ctx, "[Recovery from panic]", start,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
				}

				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}

var ignorePath = map[string]bool{
	"/metrics": true,
}

func SetLoggerMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		if ignorePath[c.Request.URL.Path] {
			return
		}

		start := time.Now()
		RequestInLog(c)
		defer requestOutLog(c, start)

		c.Next()
	}
}

func RequestInLog(c *gin.Context) {
	start := time.Now()

	path := c.Request.URL.Path

	bodyBytes, _ := ioutil.ReadAll(c.Request.Body)
	c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // Write body back

	body := string(bodyBytes)
	if len(body) > 255 {
		body = body[:255]
	}

	zaplog.Info(c, "_com_request_in", start,
		zap.Int("status", c.Writer.Status()),
		zap.String("method", c.Request.Method),
		zap.String("ip", c.ClientIP()),
		zap.String("path", path),
		zap.String("query", c.Request.URL.RawQuery),
		zap.String("user_agent", c.Request.UserAgent()),
		zap.String("body", body),
		zap.String("uri", c.Request.RequestURI),
		zap.Any("args", c.Request.PostForm),
		zap.Any("header", c.Request.Header),
	)

	c.Next()
}

func requestOutLog(c *gin.Context, start time.Time) {
	blw := c.Writer.(*CustomResponseWriter)

	body := blw.body.String()
	if len(body) > 255 {
		body = body[:255]
	}

	zaplog.Info(c, "_com_request_out", start,
		zap.String("uri", c.Request.RequestURI),
		zap.String("method", c.Request.Method),
		zap.Int("status", c.Writer.Status()),
		zap.Any("args", c.Request.PostForm),
		zap.String("ip", c.ClientIP()),
		zap.Any("response", body),
	)
}
